package edge.display.backend.MsgHandler;

import com.alibaba.fastjson.JSONObject;
import edge.display.backend.Entity.Task;
import edge.display.backend.Entity.TaskStat;
import edge.display.backend.KEY_STORE;
import edge.display.backend.MsgDistributer.WebSocket.WebSocketServer;
import edge.display.backend.Utils.BeanUtils;
import edge.display.backend.Utils.TaskUtils;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.stream.Collectors;

/**
 * 任务类数据处理中心
 *
 * @author Amane Hayaashi
 * @date 2020/10/23
 * @since 1.0
 */

@Component
public class TaskHandler {

    private static Queue<Task> taskQueue;
    private static SimpleDateFormat sdf;
    private static TaskStat taskStat;

    static {
        taskQueue = new ConcurrentLinkedDeque<>();
        sdf = new SimpleDateFormat(KEY_STORE.TASK_TIME_FORMAT);
        taskStat = new TaskStat();
        taskStat.init();
    }

    // id, type, timestamp(t1), arriveAt
    public static void onNewTask(JSONObject jsonObject) {
        // rearrange arriveAt (IP -> Index)
        TaskUtils.IP2Index(jsonObject, KEY_STORE.JSON_TASK_ARRIVE, KEY_STORE.NODE_MAP);
        // get task
        Task task = JSONObject.toJavaObject(jsonObject, Task.class);
        // get taskType
        task.setTypeAbbr(KEY_STORE.TASK_TYPE_ABBR_MAP.getOrDefault(jsonObject.getString(KEY_STORE.JSON_TASK_TYPE), null));
        // update timestamp (t1 -> timestamps)
        TaskUtils.updateTimestamps(task, jsonObject, KEY_STORE.TIMESTAMP_1);
        // update taskQ
        taskQueue.offer(task);
        // update array (tasks coming)
        RespHandler.tasksComing[task.getArriveAt()]++;
    }

    // id, timestamp(t2), offloadTo
    public static void afterTaskOffloading(JSONObject jsonObject) {
        // rearrange offloadTo (IP -> Index)
        TaskUtils.IP2Index(jsonObject, KEY_STORE.JSON_TASK_OFFLOAD, KEY_STORE.NODE_MAP);
        // get task
        Task task = JSONObject.toJavaObject(jsonObject, Task.class);
        // update timestamp (t2 -> timestamps)
        TaskUtils.updateTimestamps(task, jsonObject, KEY_STORE.TIMESTAMP_2);
        // copy property
        BeanUtils.copyProps(task, taskQueue, KEY_STORE.DEFAULT_IDENTIFIER);
    }

    // id, timestamp(t3), snapshot(file,url), result, (statistics)
    public static void afterRecognition(JSONObject jsonObject) throws IOException {
        // get task
        Task task = JSONObject.toJavaObject(jsonObject, Task.class);
        // update timestamp (t3 -> timestamps)
        TaskUtils.updateTimestamps(task, jsonObject, KEY_STORE.TIMESTAMP_3, KEY_STORE.TIMESTAMP_4);
        // copy property
        Task resTask = (Task) BeanUtils.copyProps(task, taskQueue, KEY_STORE.DEFAULT_IDENTIFIER);
        // update array (tasks executed)
        RespHandler.tasksExecuted[resTask.getArriveAt()]++;
        // RETURN if the result is null or the number of timestamp is incorrect(client collapsed)
        if (KEY_STORE.TASK_COMPOSE_LIST.size() != resTask.getTimestamps().size() - 1) {
            System.out.println("[TaskHandler] Input Task is invalid! Please check Client's MQTT");
            return;
        }
        setTaskTimeAttr(resTask);
        // set index
        resTask.setIndex(RespHandler.tasksExecuted[resTask.getArriveAt()]);
        // set offloadFlag (LC & TO)
        resTask.setOffloadFlag(resTask.getArriveAt() == resTask.getOffloadTo() ? KEY_STORE.TO_FLAG_LOCAL : KEY_STORE.TO_FLAG_TO);
        // set Pairs (1 -> 1 (LC))
        resTask.setPairs(String.format("%d &rarr; %d (%s)", resTask.getArriveAt(), resTask.getOffloadTo(), resTask.getOffloadFlag()));
        // update stat
        taskStat.update(resTask);
        // new json
        JSONObject resJSONObject = new JSONObject(new HashMap<String, Object>() {
            {
                put(KEY_STORE.JSON_KEY, KEY_STORE.WS_STAT_TASK);
                put(KEY_STORE.JSON_VALUE, taskStat);
            }
        });
        // redis
        // AsyncRedisSave asyncRedisSave = new AsyncRedisSave();
        // asyncRedisSave.forEntity(resTask, KEY_STORE.REDIS_TASK);
        // asyncRedisSave.forEntity(resTask, KEY_STORE.REDIS_TASK);
        // WebSocket
        WebSocketServer.broadcast(resJSONObject);
        // update taskQ
        taskQueue.remove(resTask);
        System.out.println("[TaskHandler] final task:" + resTask.toString());
    }

    // delayComposition, delay, efficiency, evaluation
    private static void setTaskTimeAttr(Task task) {
        // rearrange timestamps (timestamp -> formatted date)
        List<String> timestamps = task.getTimestamps();
        task.setTimestamps(timestamps.stream().map(s -> sdf.format(new Date(Long.parseLong(s)))).collect(Collectors.toList()));
        // set delayComposition
        List<Long> longList = Objects.requireNonNull(timestamps).stream().map(Long::parseLong).collect(Collectors.toList());
        // set delayComposition
        Map<String, Integer> delayCompMap = new HashMap<>();
        for (int i = 0; i < KEY_STORE.TASK_COMPOSE_LIST.size(); i++) {
            long val = Math.abs(longList.get(i + 1) - longList.get(i));
            delayCompMap.put(KEY_STORE.TASK_COMPOSE_LIST.get(i), (int) val);
        }
        task.setDelayComposition(delayCompMap);
        // set delay and efficiency
        long delay = delayCompMap.values().stream().mapToInt(Integer::intValue).sum();
        int deadline = task.getDeadline();
        float efficiency = 100 * Math.max(0f, 1 - (float) delay / (float) deadline);
        task.setDelay((int) delay);
        task.setEfficiency(efficiency);
        // set evaluation (484ms / 848ms (50%))
        task.setEvaluation(String.format("%dms / %dms(%.2f%%)", delay, deadline, efficiency));
    }
}

